package correctness;

import static org.junit.Assert.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

import model.LogicType;
import model.editormodel.Check;
import model.editormodel.EditorModel;
import model.editormodel.ErrorMessage;
import model.editormodel.Input;
import model.editormodel.Output;
import model.editormodel.ErrorMessage.ErrorType;

import org.junit.BeforeClass;
import org.junit.Test;

/**
 * <b> Mit dieser Testklasse wird Folgendes getestet:</b> <br>
 * <br>
 * Es wird eine Schaltung, in der nicht alle Eingange verbunden sind, und in der
 * kein Ausgang mit einer Lampe verbunden ist, gecheckt. Anschließend wird die
 * Schaltung korrekt vervollstaendigt.
 */
public class InputOutputCheckTest {

	private static EditorModel eModel;
	private static Check check;
	private static boolean checksOk;
	private static boolean checkThreadRunning;

	/**
	 * Diese Methode wird vor allen anderen aufgerufen. Hier wird das
	 * <code>Check</code>-Objekt initialisiert. Zuvor wird ein
	 * <code>EditorModel</code>-Objekt erzeugt.
	 */
	@BeforeClass
	public static void initialize() {
		eModel = new EditorModel();
		check = eModel.getChecker();
		checksOk = false;
		checkThreadRunning = false;
	}

	/**
	 * Testet, ob alle Ein- bzw. Ausgange einer Schaltung belegt sind.
	 */
	@Test
	public void startCheck() {

		// Fuegt einen Pseudo-Observer hinzu
		PseudoObserver pseudoObserver = new PseudoObserver();
		check.addObserver(pseudoObserver);

		// Drei Bauteile werden hinzugefuegt
		eModel.addLogicElement(LogicType.NOT, "NOT", 45, 3456);
		eModel.addLogicElement(LogicType.SWITCH, "SWITCH", 324, 973);
		eModel.addLogicElement(LogicType.LAMP, "LAMP", 108, 468);

		// Um Verbindungen setzen zu koennen, werden die Inputs und Outputs der
		// einzelnen Bauteile benoetigt. Dazu werden hier die Inputs der
		// einzelnen Bauteile gespeichert.
		Map<String, Input> notInputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_1").getInputs());
		Map<String, Input> lampInputs = new HashMap<String, Input>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("LAMP_3").getInputs());

		// Hier werden die Outputs der einzelnen Bauteile gespeichert
		Map<String, Output> notOutputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("NOT_1").getOutputs());
		Map<String, Output> switchOutputs = new HashMap<String, Output>(eModel
				.getCircuitModel().getUnmodifableElementsOnEditor()
				.get("SWITCH_2").getOutputs());

		// Fuegt eine Verbindung zwischen dem Ausgang des Schalters und dem
		// Eingang des NOT-Gatters hinzu
		eModel.setConnection(switchOutputs.get(Output.STD_O),
				notInputs.get(Input.STD_A));

		// Fuehrt die Checks aus
		checkThreadRunning = true;
		check.startCheckThread();

		// Solange die Checks laufen, soll hier gewartet werden
		while (checkThreadRunning) {
			synchronized (pseudoObserver) {
				try {
					pseudoObserver.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		// Prueft, ob die Checks fehlschlagen, wenn nicht alle Eingaenge
		// verbunden sind, bzw. kein Ausgang mit einer Lampe verbunden ist.
		assertFalse("Die Checks waren in Ordnung, obwohl nicht alle Eingaenge "
				+ "verbunden sind, bzw. kein Ausgang mit einer Lampe "
				+ "verbunden ist.", checksOk);

		// Fuegt eine Verbindung zwischen dem Ausgang des NOT-Gatters und dem
		// Eingang der Lampe hinzu. Somit ist die Schaltung korrekt
		eModel.setConnection(notOutputs.get(Output.STD_O),
				lampInputs.get(Input.STD_A));

		// Fuehrt die Checks aus
		checkThreadRunning = true;
		check.startCheckThread();

		// Solange die Checks laufen, soll hier gewartet werden
		while (checkThreadRunning) {
			synchronized (pseudoObserver) {
				try {
					pseudoObserver.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		// Prueft, ob die Checks erfolgreich sind, wenn die Schaltung korrekt
		// ist
		assertTrue("Die Checks sind fehlgeschlagen, obwohl die Schaltung "
				+ "korrekt ist.", checksOk);

	}

	/**
	 * Innere Klasse, die einen pseudoObserver zur Verfuegung stellt
	 */
	private class PseudoObserver implements Observer {

		@Override
		public void update(Observable observable, Object data) {

			if (data instanceof ErrorMessage) {
				ErrorType eType = ((ErrorMessage) data).getErrorType();

				// Waren alle Checks in Ordnung, wird checksOk auf true gesetzt,
				// andernfalls auf false
				if (eType == ErrorType.ALL_CHECKS_OK) {
					checksOk = true;
					synchronized (this) {
						checkThreadRunning = false;
						this.notifyAll();
					}
				} else if (eType == ErrorType.ONE_CHECK_FAILED) {
					checksOk = false;
					synchronized (this) {
						checkThreadRunning = false;
						this.notifyAll();
					}
				}
			}
		}
	}
}